package me.magicall.db.springjdbc;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;

import me.magicall.coll.ElementTransformer;
import me.magicall.db.dao.CommonDao;
import me.magicall.db.dao.Dao;
import me.magicall.db.meta.DbColumn;
import me.magicall.db.meta.TableMeta;
import me.magicall.db.meta.TableMetaAccessor;
import me.magicall.db.util.DbUtil;
import me.magicall.db.util.FieldType;
import me.magicall.db.util.PageInfo;
import me.magicall.db.util.ReturnKeyAndAffect;
import me.magicall.tagInterfaces.Wrapper;
import me.magicall.util.ObjectUtil;
import me.magicall.util.kit.Kits;

import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;

public abstract class UsingTableMetaDao<T> implements Dao<T> {

//	protected final RowMapper<T> defaultRowMapper = new RowMapper<T>() {
//		@Override
//		public T mapRow(final ResultSet resultSet, final int rowNum) throws SQLException {
//			final TableMeta tableMeta = getTableMeta();
//			final Object idFromResultSet = DbUtil.getIdFromResultSet(resultSet, tableMeta);
//			return mappingRowWithoutId(newT(idFromResultSet), resultSet, rowNum, null);
//		}
//	};

	protected class CommonDaoWrapper extends SpringJdbcCommonDao<T> implements Wrapper {

		public CommonDaoWrapper(final TableMetaAccessor tableMetaAccessor, final NamedParameterJdbcOperations jdbc) {
			super(tableMetaAccessor, jdbc);
		}

		@Override
		protected TableMeta getTableMetaUsingModel(final T model) {
			return getTableMeta();
		}

		@Override
		protected TableMeta getTableMetaUsingModels(final List<T> list) {
			return getTableMeta();
		}

		@Override
		protected Object getProperty(final T model, final String fieldName) {
			return UsingTableMetaDao.this.getProperty(model, fieldName);
		}

		@Override
		protected void setProperty(final T model, final String fieldName, final Object value) {
			UsingTableMetaDao.this.setProperty(model, fieldName, value);
		}
	}

//=================================================
	protected TableMeta tableMeta;
	protected NamedParameterJdbcOperations jdbc;

	protected CommonDaoWrapper commonDao;

	public UsingTableMetaDao(final TableMeta tableMeta, final NamedParameterJdbcOperations jdbc, final TableMetaAccessor tableMetaAccessor) {
		super();
		this.tableMeta = tableMeta;
		this.jdbc = jdbc;
		this.commonDao = new CommonDaoWrapper(tableMetaAccessor, jdbc);
	}

	public UsingTableMetaDao() {
		super();
	}

	public TableMeta getTableMeta() {
		return tableMeta;
	}

	public void setTableMeta(final TableMeta tableMeta) {
		this.tableMeta = tableMeta;
	}

	public NamedParameterJdbcOperations getJdbc() {
		return jdbc;
	}

	public void setJdbc(final NamedParameterJdbcOperations jdbc) {
		this.jdbc = jdbc;
	}

	public CommonDao<T> getCommonDao() {
		return commonDao;
	}

//	public void setCommonDao(final CommonDao<T> commonDao) {
//		this.commonDao = commonDao;
//	}

	//=============================================
	@Override
	public int allCount() {
		final TableMeta tableMeta = getTableMeta();

		final StringBuilder sb = new StringBuilder("select count(1) from ").append(tableMeta.getName());

		return getJdbc().getJdbcOperations().queryForInt(sb.toString());
	}

	@Override
	public Collection<T> all() {
		final TableMeta tableMeta = getTableMeta();

		final StringBuilder sb = DbUtil.buildSql_SelectAllFrom(tableMeta);

		final RowMapper<T> rowMapper = new RowMapper<T>() {
			@Override
			public T mapRow(final ResultSet resultSet, final int rowNum) throws SQLException {
				final T model = newT(DbUtil.getIdFromResultSet(resultSet, tableMeta));
				return commonDao.mappingRowWithoutId(model, resultSet, rowNum);
			}
		};
		return getJdbc().getJdbcOperations().query(sb.toString(), rowMapper);
	}

	public int count(final Map<String, ?> paramMap) {
		final TableMeta tableMeta = getTableMeta();
		final StringBuilder sb = new StringBuilder("select count(1) from ").append(tableMeta.getName());
		final Map<String, Object> finalParamMap = buildParamMap(paramMap, tableMeta, sb);
		return getJdbc().queryForInt(sb.toString(), finalParamMap);
	}

	private Map<String, Object> buildParamMap(final Map<String, ?> paramMap, final TableMeta tableMeta, final StringBuilder sb) {
		final Map<String, Object> finalParamMap;

		final List<DbColumn> columns = tableMeta.getColumns();

		if (Kits.MAP.isEmpty(paramMap)) {
			finalParamMap = Kits.MAP.emptyValue();
		} else {
			finalParamMap = new HashMap<String, Object>(paramMap.size());
			sb.append(" where 1=1");
			for (final DbColumn dbColumn : columns) {
				final String dbColumnName = dbColumn.getName().toLowerCase();
				Object value = null;
				//是否是表中的字段
				boolean contains = false;
				if (paramMap.containsKey(dbColumnName)) {
					contains = true;
					value = paramMap.get(dbColumnName);
				} else {
					final String fieldName = DbUtil.dbNameToJavaName(dbColumnName);
					if (paramMap.containsKey(fieldName)) {
						contains = true;
						value = paramMap.get(fieldName);
					}
				}
				if (contains) {
					if (value == null) {
						if (dbColumn.getNullable()) {
							sb.append(" and ").append(dbColumnName);
							sb.append(" is null ");
						} else {
							continue;
						}
					} else {
						sb.append(" and ").append(dbColumnName);
						final List<?> list = ObjectUtil.castToList(value);
						final int size = list.size();
						final FieldType fieldType = dbColumn.getType();
						if ((fieldType == FieldType.DATE || fieldType == FieldType.TIME || fieldType == FieldType.TIMESTAMP)//
								&& size == 2 && list.get(0) instanceof Date && list.get(1) instanceof Date) {
							//时间字段的between操作
							final String startTime = dbColumnName + "1";
							final String endTime = dbColumnName + "2";
							sb.append(" between :").append(startTime).append(" and :").append(endTime);
							finalParamMap.put(startTime, list.get(0));
							finalParamMap.put(endTime, list.get(1));
						} else if (size > 1) {
							//字段的in操作
							sb.append(" in(");
							int index = 0;
							for (final Object o : list) {
								final String paramName = dbColumnName + index;
								sb.append(":").append(paramName).append(",");
								finalParamMap.put(paramName, o);
								++index;
							}
							sb.deleteCharAt(sb.length() - 1).append(")");
						} else if (size > 0) {
							sb.append("=:").append(dbColumnName);
							finalParamMap.put(dbColumnName, list.get(0));
						}
					}
				}
			}//for
		}
		return finalParamMap;
	}

	private List<T> getList(final Map<String, ?> paramMap, final PageInfo pageInfo, final boolean asc) {
		if (pageInfo.getSize() < 1) {
			return Kits.LIST.emptyValue();
		}
		final TableMeta tableMeta = getTableMeta();
		final List<String> dbColumnNames = tableMeta.getColumnNames();

		final StringBuilder sb = DbUtil.buildSql_SelectAllFrom(tableMeta.getName(), dbColumnNames);

		final Map<String, Object> finalParamMap = buildParamMap(paramMap, tableMeta, sb);
		orderById(sb, asc);
		final String sql = DbUtil.appendLimit(sb, pageInfo).toString();
		return getJdbc().query(sql, finalParamMap, new RowMapper<T>() {

			@Override
			public T mapRow(final ResultSet rs, final int rowNum) throws SQLException {
				final TableMeta tableMeta = getTableMeta();
				final Object idFromResultSet = DbUtil.getIdFromResultSet(rs, tableMeta);
				return commonDao.mappingRowWithoutId(newT(idFromResultSet), rs, rowNum);
			}
		});//// rowMapper());
	}

	@Override
	public int del(final Comparable<?> id) {
		final TableMeta tableMeta = getTableMeta();

		final StringBuilder sb = new StringBuilder("delete from ").append(tableMeta.getName())//
				.append(" where ").append(DbUtil.COMMON_ID_FIELD_NAME).append("=?");

		return getJdbc().getJdbcOperations().update(sb.toString(), id);
	}

	@Override
	public T get(final Comparable<?> id) {
		return fill(newT(id));
	}

	@Override
	public Collection<T> listUsingIds(final Collection<? extends Object> ids) {
		final Collection<Object> ids0 = new HashSet<Object>(ids.size());
		for (final Object id : ids) {
			if (id != null) {
				ids0.add(id);
			}
		}
		if (ids0.isEmpty()) {
			return Kits.COLL.emptyValue();
		}
		return fill(Kits.COLL.transform(ids0, new ElementTransformer<Object, T>() {
			@Override
			public T transform(final int index, final Object id) {
				return newT(id);
			}
		}));
	}

	@Override
	public Collection<T> listUsingIds(final Object... ids) {
		return listUsingIds(Arrays.asList(ids));
	}

	@Override
	public List<T> getList(final Map<String, ?> paramMap, final PageInfo pageInfo) {
		return getList(paramMap, pageInfo, false);
	}

	@Override
	public List<T> getList(final Map<String, ?> paramMap, final int size) {
		return getList(paramMap, PageInfo.offsetSize(0, size));
	}

	@Override
	public List<T> getList(final Map<String, ?> paramMap) {
		return getList(paramMap, Integer.MAX_VALUE);
	}

	//---------------------------------------------------

	@Override
	public boolean add(final T model) {
		return commonDao.add(model);
	}

	@Override
	public int[] add(final T... models) {
		return commonDao.add(models);
	}

	@Override
	public int[] add(final Collection<? extends T> models) {
		return commonDao.add(models);
	}

	@Override
	public ReturnKeyAndAffect addReturnKey(final T model) {
		return commonDao.addReturnKey(model);
	}

	@Override
	public int[] addOrUpdate(final Collection<? extends T> models) {
		return commonDao.addOrUpdate(models);
	}

	@Override
	public int[] addOrUpdate(final T... models) {
		return commonDao.addOrUpdate(models);
	}

	@Override
	public boolean addOrUpdate(final T model) {
		return commonDao.addOrUpdate(model);
	}

	@Override
	public int[] del(final Collection<? extends T> models) {
		return commonDao.del(models);
	}

	@Override
	public int[] del(final T... models) {
		return commonDao.del(models);
	}

	@Override
	public int del(final T model) {
		return commonDao.del(model);
	}

	@Override
	public T fill(final T modelOnlyWithId) {
		return commonDao.fill(modelOnlyWithId);
	}

	@Override
	public Collection<T> fill(final Collection<T> modelOnlyWithId) {
		return commonDao.fill(modelOnlyWithId);
	}

	@Override
	public Collection<T> fill(final T... modelOnlyWithId) {
		return commonDao.fill(modelOnlyWithId);
	}

	@Override
	public int[] update(final Collection<? extends T> models) {
		return commonDao.update(models);
	}

	@Override
	public int[] update(final T... models) {
		return commonDao.update(models);
	}

	@Override
	public int update(final T model) {
		return commonDao.update(model);
	}

	@Override
	public ReturnKeyAndAffect addReturnKey(final T... models) {
		return commonDao.addReturnKey(models);
	}

	@Override
	public ReturnKeyAndAffect addReturnKey(final Collection<? extends T> models) {
		return commonDao.addReturnKey(models);
	}

	@Override
	public int addIfAbsent(final T... models) {
		return commonDao.addIfAbsent(models);
	}

	@Override
	public int addIfAbsent(final Collection<? extends T> models) {
		return commonDao.addIfAbsent(models);
	}

	//===================================================
//	protected RowMapper<T> rowMapper() {
//		return defaultRowMapper;
//	}

	protected T newT(final Object id) {
		final T model = newT();
		setProperty(model, idFieldName(), id);
		return model;
	}

//	protected T mappingRowWithoutId(final T modelOnlyWithId, final ResultSet resultSet, final int rowNum, final GetValueFromResultSet getValueFromResultSet)
//			throws SQLException {
//		final ResultSetMetaData resultSetMetaData = resultSet.getMetaData();
//		final int columnCount = resultSetMetaData.getColumnCount();
//		for (int i = DbUtil.RESULT_SET_COLUMN_START_INDEX; i <= columnCount; ++i) {
//			final String label = resultSetMetaData.getColumnLabel(i);
//			if (idFieldName().equalsIgnoreCase(label)) {//id 字段不再设
//				continue;
//			}
//			setProperty(modelOnlyWithId, DbUtil.dbNameToJavaName(label),
//					getValueFromResultSet == null ? resultSet.getObject(i) : getValueFromResultSet.getValue(resultSet, i, label));
//		}
//		return modelOnlyWithId;
//	}

	protected StringBuilder orderById(final StringBuilder sb, final boolean asc) {
		return sb.append(" order by ").append(idColumnName()).append(asc ? " asc " : " desc ");//
	}

	protected String idFieldName() {
		return DbUtil.COMMON_ID_FIELD_NAME;
	}

	protected String idColumnName() {
		return DbUtil.COMMON_ID_COLUMN_NAME;
	}

	protected abstract Object getProperty(final T model, final String fieldName);

	protected abstract void setProperty(final T model, final String fieldName, final Object value);

	protected abstract T newT();

}